<?php
include_once(_CORE_PATH. "mapper_prototype.php");
include_once(_APP_PATH. "business/media/media_entity.php");
include_once(_APP_PATH. "business/articles/article_filter.php");
include_once(_APP_PATH. "business/media/media_processor.php");

class MediaMapper extends mapper_prototype
{
	private $_source;
	
	public function __construct()
	{
		parent::__construct('media');
		
		$this->_source = MozgSource::get('default');
	}	
	
	static public function GetInstance()
	{
		return new self();		
	}

	private function GetByProperty($prop, $value)
	{
          return $this->_source->query("SELECT
												id,
												title,
												description,
												tags,
												file,
												type,
												length,
												thumbnail,
												status,
												date_created dateCreated,
												date_published publishDate,
												filesize,
												is_blocked isBlocked									   
						                     FROM
						                     	%%media%%
						                     WHERE
						                     	" .$prop. "=" . $this->_source->quote($value) . "
						                    ")->fetchObject('MediaEntity');			
	}
	
	public function GetById($id)
	{
		return $this->GetByProperty('id', $id);	
	}

	public function GetByCode($code)
	{
		return $this->GetByProperty('code', $code);	
	}	
	
	public function GetNew()
	{
		$obj = new MediaEntity();
		$obj->SetStatus(0);
		return $obj;
	} 
		
	public function GetMediaFilter()
	{
		return new ArticleEntryFilter();
	} 	
	
	private function GetSqlFromFilter(ArticleEntryFilter $filter)
	{
		$sqlFilter = array();
		
		$tagsArr = $this->GetTagsArrFromStr($filter->GetTags());
		if (sizeof($tagsArr)) 
		{
			$searchStr = '';
			foreach ($tagsArr as $tag)
			{
				$searchStr .= '+",' .trim($this->_source->quote($tag), "'"). '," ';
			}
			$sqlFilter['where'][] = "MATCH(mm.tags_norm) AGAINST('" .$searchStr. "' IN BOOLEAN MODE)";
		}
		
		if ($filter->GetFromDate())
		{
			$sqlFilter['where'][] = 'mm.date_published >= ' .$this->_source->quote($filter->GetFromDate('Y-m-d H:i'));
		}
		
		if ($filter->GetToDate())
		{
			$sqlFilter['where'][] = 'mae.date_published <= ' .$this->_source->quote($filter->GetToDate('Y-m-d H:i'));
		}		
		
		if (sizeof($sqlFilter['where'])) $sqlFilter['where'] = implode(' and ', $sqlFilter['where']);
		
		return $sqlFilter;
	}	
	
	private $_totalRows = 0;
	
	public function GetTotal()
	{
		return $this->_totalRows;		
	}
	
	public function GetMedia($paginator, ArticleEntryFilter $filter = null)
	{   
		$sqlFilter = '';
		if ($filter)
		{
			$sqlFilter = $this->GetSqlFromFilter($filter);			
		}		
		
        $stmt = $this->_source->query("SELECT SQL_CALC_FOUND_ROWS
												mm.id,
												mm.title,
												mm.tags,
												mm.file,
												mm.type,
												mm.length,
												mm.thumbnail,
												mm.status,
												mm.date_created dateCreated,
												mm.date_published publishDate,
												mm.filesize,
												mm.is_blocked isBlocked	
							                 FROM 
							                 	%%media%% mm
							                 " .($sqlFilter['where'] ? " WHERE (" .$sqlFilter['where']. ") " : ""). "	        																 
							                 ORDER BY mm.date_published DESC
							                 " .($paginator ? "LIMIT " .$paginator->GetSqlLimit() : ""). "
							               ");

        $this->_totalRows = $this->_source->query("SELECT FOUND_ROWS()")->fetchColumn();
        
	    $stmt->setFetchMode(PDO::FETCH_CLASS, 'MediaEntity');
        return $stmt->fetchAll();		
	}	

	public function Save(MediaEntity $obj)
	{
		if (!$this->Prepare($obj)) return false;
		
		if ($obj->GetId())
		{
			return $this->Update($obj);		
		}

		return $this->Insert($obj);
	}
	
	private function Insert(MediaEntity $obj)
	{		
		$tagsArr = $this->GetTagsArrFromStr($obj->GetTags());
		$tagsNorm = $this->GetTagsNorm($tagsArr);		
		
		$num = $this->_source->exec("insert ignore into %%media%%(title, description, tags, file, type, date_created, date_published, filesize, thumbnail, length, status, code, is_blocked, tags_norm)
			            values(
					              " .$this->_source->quote($obj->GetTitle()). ",
					              " .$this->_source->quote($this->Libs()->str->EscapeAbsoluteUrls($obj->GetDescription())). ",
					              " .$this->_source->quote($obj->GetTags()). ",
					              " .$this->_source->quote($obj->GetFile()). ",
					              " .$this->_source->quote($obj->GetType()). ",
					              NOW(),
					              " .$this->_source->quote($obj->GetPublishDate('Y-m-d H:i')). ",
					              " .(int)$obj->GetFileSize(). ",
					              " .$this->_source->quote($obj->GetThumbNail()). ",
					              " .$this->_source->quote($obj->GetLength()). ",
					              " .(int)$obj->GetStatus(). ",
					              " .(is_null($obj->GetCode()) ? 'NULL' : $this->_source->quote($obj->GetCode())). ",
					              1,
					              " .$this->_source->quote($tagsNorm). "  
			            )
			");		
		
		if ($num)
		{
			$obj->SetId($this->_source->lastInsertID());
			$this->SetMediaTags($obj->GetId(), $tagsNorm);

			return true;
		}
			
		$this->DelFiles($obj->GetFile());
		
		return false;
	}
	
	private function Update(MediaEntity $obj)
	{
		$tagsArr = $this->GetTagsArrFromStr($obj->GetTags());
		$tagsNorm = $this->GetTagsNorm($tagsArr);		
		
		$num = (bool)$this->_source->exec("update %%media%%
										      set
												 title = " .$this->_source->quote($obj->GetTitle()). ",
												 description = " .$this->_source->quote($this->Libs()->str->EscapeAbsoluteUrls($obj->GetDescription())). ",
												 tags = " .$this->_source->quote($obj->GetTags()). ",
												 filesize = " .(int)$obj->GetFileSize(). ",
												 length = " .$this->_source->quote($obj->GetLength()). ",
												 status = " .(int)$obj->GetStatus(). ",
												 code = " .(is_null($obj->GetCode()) ? 'NULL' : $this->_source->quote($obj->GetCode())). ",
												 is_blocked = " .(int)$obj->GetIsBlocked(). ",
												 tags_norm = " .$this->_source->quote($tagsNorm). ",
												 date_published = " .$this->_source->quote($obj->GetPublishDate('Y-m-d H:i')). "
											  where 
										      	 id=" .$obj->GetId(). "
									     ");		
		
		if ($num)
		{
			$this->SetMediaTags($obj->GetId(), $tagsNorm);
			return true;
		}
	
		return false;
	}	

    private function GenerateFileName()
    {
        return md5(uniqid(rand(), true));
    }	
    
    private function GetMediaFolder()
    {
    	return date('Y/m/d/H/');
    }

	private function Prepare(MediaEntity $obj)
	{
		if (!$obj->GetId())
		{
			$uploadDir = _ROOT_PATH.MozgCfg::GetProp('global/media//mediaImageFolder'). 'temp/';
			
			if ($uploadedData = $obj->GetTempFile()) 
			{				
				if ($uploadedData['error'] != UPLOAD_ERR_OK) return $this->SetErrorMessage($this->GetUploadErrorMessage($uploadedData['error']));
				
				$ext = pathinfo($uploadedData['name'], PATHINFO_EXTENSION);
	
				$type = MediaProcessor::GetMediaTypeByExt($ext); //video, audio, photo
				if (!$type) return $this->SetErrorMessage('Неподдерживаемый формат файла');
				
				$obj->SetCode($this->GenerateFileName());
				$newFileName = $obj->GetCode(). '.' .$ext;
				
				if (!$this->Libs()->dir->Create($uploadDir)) return $this->SetErrorMessage('Произошла ошибка при закачке файла');				
				
				$newFullFilePath = $uploadDir.$newFileName;
				
				if (!move_uploaded_file($uploadedData['tmp_name'], $newFullFilePath))
				{
					return $this->SetErrorMessage('Произошла ошибка при закачке файла');			
				}
			}
			else if ($fileName = $obj->GetLocalUrlFile())
			{
				if (0 === strpos($fileName, _ROOT_URL))
				{
					$fileName = substr($fileName, strlen(_ROOT_URL));
				}

				$fileName = _ROOT_PATH.$fileName;
				
				if (!is_file($fileName))
				{
					return $this->SetErrorMessage('Файл не найден');
				}				
				
				$ext = pathinfo($fileName, PATHINFO_EXTENSION);
	
				$type = MediaProcessor::GetMediaTypeByExt($ext); //video, audio, photo
				if (!$type) return $this->SetErrorMessage('Неподдерживаемый формат файла');
				
				$obj->SetCode($this->GenerateFileName());
				$newFileName = $obj->GetCode(). '.' .$ext;
					
				$newFullFilePath = $uploadDir.$newFileName;
				copy($fileName, $newFullFilePath);						
			}
			else
			{
				return $this->SetErrorMessage('Не выбран файл');
			}
						
			try
			{
				$handler = MediaProcessor::GetHandler($type, $newFullFilePath);
			}
			catch (Exception $e)
			{
				@unlink($newFullFilePath);
				return $this->SetErrorMessage($e->getMessage());
			}			
			
			$dirname = _ROOT_PATH.MozgCfg::GetProp('global/media//mediaImageFolder').$this->GetMediaFolder();			
			$handler->SetDestPath($dirname);
			
			$newFileName = $handler->Convert($obj->GetCode());
			if ($handler->IsError())
			{
				@unlink($newFullFilePath);
				return $this->SetErrorMessage($handler->GetErrorMessage());
			}		

			$newFileName = $this->GetMediaRelativeFilePath($newFileName);
			$thumbNail = $this->GetMediaRelativeFilePath($handler->CreateThumbNail());
			
			$obj->SetStatus($handler->GetConvertStatus());
			$obj->SetType($type);
			$obj->SetThumbNail($thumbNail);				
			$obj->SetLength($handler->GetFileLength());
			$obj->SetFile($newFileName);
			$obj->SetFileSize($handler->GetFileSize());
        }				
		
        $obj->SetDescription($this->ClearText($obj->GetDescription()));
        
        return true;
	}

    private function ClearText($text)
    {
        return $this->Libs()->minifier->MinifyHTML($this->Libs()->purifier->purifyHTML($text));
    }		
	
	private function GetMediaRelativeFilePath($fullFilePath)
	{
		$fullFilePath = str_replace('\\', '/', $fullFilePath);
		//$mediaPath = str_replace('\\', '/', _ROOT_PATH.MozgCfg::GetProp('global/media//mediaImageFolder'));
		$mediaPath = str_replace('\\', '/', _ROOT_PATH);
		
		if (0 === strpos($fullFilePath, $mediaPath))
		{
			$fullFilePath = substr($fullFilePath, strlen($mediaPath));
		}	

		return $fullFilePath;
	}
	
    private function GetUploadErrorMessage($errorCode)
    {
        switch ($errorCode)
        {
            case UPLOAD_ERR_FORM_SIZE:
            case UPLOAD_ERR_INI_SIZE:
	            $m = 'Превышен размер загружаемого файла';    
	            break;
            
            case UPLOAD_ERR_PARTIAL:
                $m = 'Загружаемый файл был получен только частично';
                break;
            
            case UPLOAD_ERR_NO_FILE:
                $m = 'Файл не был загружен';
                break;
            
            default: $m = 'Неизвестная ошибка';
                                    
        }
        
        return $m;
    }	
	
    public function ApproveMediaByCode($code)
    {
    	$obj = $this->GetByCode($code);
    	if (!$obj) return false;
    	
    	$filePath = _ROOT_PATH.$obj->GetFile();
    	
    	$fileCheckResult = false;
    	
   		try
		{
			$handler = MediaProcessor::GetHandler($obj->GetType(), $filePath);
			
			$obj->SetStatus(MatchMediaStatus::Ready);	
			$obj->SetLength($handler->GetFileLength());
			$obj->SetFileSize($handler->GetFileSize());

			$fileCheckResult = true;
		}
		catch (Exception $e)
		{	
			$this->SetErrorMessage($e->getMessage());
			
			$obj->SetStatus(MatchMediaStatus::HasError);
		}    	
    	
		$obj->SetIsBlocked(0);
		$obj->SetCode(null);
		
		$updateResult = $this->Update($obj);
		
		return $updateResult && $fileCheckResult;
    }
    
    public function Delete($id)
    {
    	$obj = $this->GetById($id);
    	if (!$obj) return false;
    	
        $num = (bool)$this->_source->exec("DELETE 
        											mm, mme
		        								FROM 
		        									%%media%% mm
		        										left join %%media_ext%% mme on(mm.id = mme.media_id)
		        										left join %%media_tag%% mmt on(mm.id = mmt.media_id)
		        								WHERE 
		        									mm.id=" .(int)$id. " and
		        									(mm.status != " .MatchMediaStatus::NotProcessed. " or !mm.is_blocked)
		        								");
    	if ($num)
    	{	
    		$this->DelFiles(array($obj->GetFile(), $obj->GetThumbnail()));
    		return true;
    	}
    	
    	return false;    
    } 

	private function GetTagsNorm(array $tagsArr)
	{
		$tagsNorm = ',';
		foreach ($tagsArr as $tag)
		{
			$tagsNorm .= $tag. ',';
		}

		return $tagsNorm;
	}    
    
	public function GetTagsArrFromStr($tagsList)
	{
		return array_filter(array_unique(array_map('trim', explode(',', $tagsList))), array($this, 'CheckTagName'));		
	}
	
	private function SetMediaTags($mediaId, $tagsArr = '')
	{
		$mediaId = (int)$mediaId;
		$this->_source->exec("delete 
									from 
										%%media_tag%%
					            	where
					            		media_id = " .$mediaId );
		
		if (!is_array($tagsArr)) $tagsArr = $this->GetTagsArrFromStr($tagsArr);  

		if (sizeof($tagsArr))
		{
			$sqlPreparedTags = array_map(array($this->_source, 'quote'), $tagsArr);
			
			$this->_source->exec("insert ignore into %%tag%%(name)
				            values
				            (" .implode("),(", $sqlPreparedTags). ")");	
			
			$foundTags = $this->_source->query("SELECT
														id							   
								                     FROM
								                     	%%tag%%	
								                     WHERE
								                     	name in (" .implode(",", $sqlPreparedTags). ")
								                     ")->fetchAll(PDO::FETCH_COLUMN);		
			if (sizeof($foundTags))
			{
				$insSql = '';
				foreach ($foundTags as $tagId) 
				{
					$insSql .= '(' .$mediaId. ', ' .$tagId. '),';										
				}
				
				$this->_source->exec("insert ignore into %%media_tag%%(media_id, tag_id)
					            values
					            " .rtrim($insSql, ','));					
			}
		}		
 		      					
	}
	
	public function CheckTagName($name)
	{
		return $this->Libs()->str->Len($name) > 1;				
	} 

    private function DelFiles($files)
    {
    	if (!$files) return;
		if (!is_array($files)) $files = array($files);  
   	    foreach ($files as $file)
       	{
			if (!$file || 
			(in_array(pathinfo($file, PATHINFO_DIRNAME), array('.', '/', '\\'))) ||
			!is_file(_ROOT_PATH.$file)) continue;
			@unlink(_ROOT_PATH.$file);        		
       	}   		  	
    }

    public function GetExtMedia($extId, $extMod)
    {
		$extId = (int)$extId;
		$extMods = MozgCfg::GetProp('global/media//extMods');
		
		if (!$extId || !isset($extMods[$extMod]) || !$extMods[$extMod]['id']) return array();    	
		
		$extModId = (int)$extMods[$extMod]['id'];
		
        $stmt = $this->_source->query("SELECT
												mm.id,
												mm.title,
												mm.type,
												mm.thumbnail
							                 FROM 
							                 	%%media%% mm,
							                 	%%media_ext%% mme
							                 WHERE
							                 	mm.id = mme.media_id and
							                 	mme.ext_id = " .$extId. " and
							                 	mme.ext_mod_id = " .$extModId. "	              																 
							                 ORDER BY mm.date_created DESC
							               ");

	    $stmt->setFetchMode(PDO::FETCH_CLASS, 'MediaEntity');
        return $stmt->fetchAll();		
    }
    
    public function SaveExtMedia($extId, $extMod, $mediaFileIds)
    {
		$extId = (int)$extId;
		
		$extModData = MozgCfg::GetProp('global/media//extMods/' .$extMod);
		if (!is_array($extModData)) return false;
		
		$extModId = (int)$extModData['id'];
		
		$this->_source->exec("delete 
									from 
										%%media_ext%%
					            	where
					            		ext_id = " .$extId. " and
					            		ext_mod_id = " .$extModId );

		$this->AddExtMedia($extId, $extMod, $mediaFileIds);
    }
    
    public function AddExtMedia($extId, $extMod, $mediaFileIds)
    {
		$extModData = MozgCfg::GetProp('global/media//extMods/' .$extMod);
		if (!is_array($extModData)) return false;    	
		$extModId = (int)$extModData['id'];
    	
    	$extId = (int)$extId;
		$mediaFileIds = array_map('intval', $mediaFileIds);
		
		if (!$extId || !$extModId || !sizeof($mediaFileIds)) return false;
		
		$insSql = '';
		foreach ($mediaFileIds as $mediaFileId) 
		{
			$insSql .= '(' .$mediaFileId. ', ' .$extId. ', ' .$extModId. '),';
		}
		
		$this->_source->exec("insert ignore %%media_ext%%(media_id, ext_id, ext_mod_id)
			            values
			            " .rtrim($insSql, ','));	
					
		return true;    	
    }
    
	public function DelExtMedia($extId, $extMod, $mediaFileId)
	{
		$extModData = MozgCfg::GetProp('global/media//extMods/' .$extMod);
		if (!is_array($extModData)) return false;		
		$extModId = (int)$extModData['id'];
		
		$mediaFileId = (int)$mediaFileId;
		$extId = (int)$extId;

		if (!$extId || !$extModId || !$mediaFileId) return false;
		
        return (bool)$this->_source->exec("DELETE 
        											mme
		        								FROM 
		        									%%media_ext%% mme
		        								WHERE 
		        									mme.media_id=" .$mediaFileId. " and
		        									mme.ext_id=" .$extId. " and
		        									mme.ext_mod_id=" .$extModId. "
		        								");		
	}    
}	